<?php

/**
 * @desc Work with security subject. 
 *
 */
abstract class Security_Utils_Subject 
{

    /**
     * Код действия логина.
     *
     * @var integer
     */
    const ACTION_LOGIN = 0;

    /**
     * @desc Mapping of the global roles for their tables. 
     * 
     * 
     * @var array
     */
    static private $_tables = array
    (
        Security_Subject::USER => 'user_user',
        Security_Subject::ADMIN => 'system_admin',
    );

    /**
     * @desc Create new security subject. 
     *
     * @param   string  $email
     * @param   string  $password
     * @param   boolean $non_encrypted
     * @return  integer
     */
    static public function create($email, $password, $non_encrypted = false) 
    {
        return database::insert('system_subject', array(
            'email' => $email,
            'password' => $non_encrypted ? $password : self::_encrypt($password),
        ));
    }

    /**
     * @desc Reset password. 
     *
     * @param   integer $account_id
     * @param   integer $role
     * @return  boolean
     */
    static public function reset($account_id, $role) 
    {
        // update user profile
        return self::setPassword($account_id, $password = self::generatePassword())
            // and send email
            && self::_send($account_id, 'new-password', $role, array('password' => $password));
    }
    
    /**
     * @desc Generate password of length specified. If not specified take avarage
     * value from settings from min and max password length values. 
     *
     * @param   integer $length
     * @return  string
     */
    static public function generatePassword($length = null) 
    {
        if (!$length) {
            // define password length settings 
            list($min, $max) = array_values(Kernel_Config::get('security', array('password_min_length', 'password_max_length')));
            $length = rand($min, $max);
        }
        
        // whether password is pronounceable
        $pronounceable = (boolean)Kernel_Config::get('security', 'password_pronounceable');
        
        // generate
        $password = Security_Utils_Password::generate($length, $pronounceable);
        
        // check for duplicates
        $query = '
            SELECT 
                   COUNT(*) 
              FROM 
                   `system_subject` 
             WHERE 
                   `password` = "' . self::_encrypt($password) . '"
            ';
        if (database::query($query) > 0) {
            self::generatePassword($length);
        } else {
            return $password;
        }
    }
    
    /**
     * @desc Update email address for the specified security account. 
     *
     * @param   integer  $account_id
     * @param   string   $password
     * @return  boolean
     */
    static public function setEmail($account_id, $email) 
    {
        return database::update('system_subject', array(
            'email' => $email,
        ), (int)$account_id) > 0;
    }

    /**
     * @desc Update password address for the specified security account. 
     *
     * @param   integer  $account_id
     * @param   string   $password
     * @return  boolean
     */
    static public function setPassword($account_id, $password) 
    {
        return database::update('system_subject', array(
            'password' => self::_encrypt($password),
        ), (int)$account_id) > 0;
    }

    /**
     * @desc Password validation. 
     *
     * @param   integer  $subject_id
     * @param   string   $password
     * @return  boolean
     */
    static public function isPasswordValid($subject_id, $password) 
    {
        $query = 'SELECT
                         `password`
                    FROM
                         `system_subject`
                   WHERE
                         `id` = ' . (int)$subject_id;

        return false !== ($encrypted = database::fetchOne($query))
            && self::compare($password, $encrypted);
    }

    /**
     * @desc Return security subject ID according to the specified profile ID. 
     *
     * @param   integer $profile
     * @param   integer $role
     * @return  integer
     */
    static public function getSubjectByProfile($profile, $role) 
    {
        $query = 'SELECT
                         `subject_id`
                    FROM
                         `' . self::$_tables[$role] . '`
                   WHERE
                         `id` = "' . (int)$profile . '"';
        return database::fetchOne($query);
    }

    /**
     * @desc Return profile ID according to the specified security subject. 
     * The last one can be ID or email. 
     *
     * @param   mixed   $account_id
     * @param   integer $role
     * @return  integer
     */
    static public function getProfileId($account_id, $role) 
    {
        $query = 'SELECT
                         `p`.`id`
                    FROM
                         `' . self::$_tables[$role] . '` `p`
              INNER JOIN
                         `system_subject` `s`
                      ON
                         `s`.`id` = `p`.`subject_id`
                   WHERE';

        // if ID
        if (is_numeric($account_id)) {
            $query .= '
                         `s`.`id` = ' . (int)$account_id;
        } elseif (is_string($account_id)) {
            // else email 
            $query .= '
                         `s`.`email` = "' . database::escape($account_id) . '"';
        } else {
            trigger_error('Unsupporded argument type', E_USER_ERROR);
        }

        return database::fetchOne($query);
    }

    /**
     * @desc Return security questions for the specified security subject. 
     *
     * @param   integer $subject
     * @return  array
     */
    static public function getQuestions($subject) 
    {
        $query = 'SELECT
                         `position`,
                         `question`,
                         `answer`
                    FROM
                         `system_subject_question`
                   WHERE
                         `subject_id` = ' . (int)$subject . '
                   LIMIT
                         ' . Kernel_Config::get('security', 'questions');

        return database::fetchAssoc($query);
    }

    /**
     * @desc Return whether security questions are available. 
     *
     * @return  array
     */
    static public function getAvailableQuestions() 
    {
        $query = 'SELECT
                         `id`,
                         `question`
                    FROM
                         `system_question`
                ORDER BY
                         `id`';

        return database::fetchPairs($query);
    }

    /**
     * @desc Return whether security questions are defined for security subject 
     * email. 
     * 
     *
     * @param   string  $email
     * @return  boolean
     */
    static public function hasQuestions($email) 
    {
        $query = 'SELECT
                         COUNT(*)
                    FROM
                         `system_subject_question` `q`
              INNER JOIN
                         `system_subject` `s`
                      ON
                         `s`.`id` = `q`.`subject_id`
                   WHERE
                         `s`.`email` = "' . database::escape($email) . '"';

        return database::fetchOne($query) > 0;
    }

    /**
     * @desc Return security subject ID by its email. 
     *
     * @param   string  $email
     * @return  integer
     */
    static public function getSubjectByEmail($email) 
    {
        $query = 'SELECT
                         `id`
                    FROM
                         `system_subject` `s`
                   WHERE
                         `email` = "' . database::escape($email) . '"';

        return database::fetchOne($query);
    }

    /**
     * @desc Compare password with encrypted one. 
     *
     * @param   string  $password
     * @param   string  $encrypted
     * @return  string
     */
    static public function compare($password, $encrypted) 
    {
        return self::_encryption()->compare($password, $encrypted);
    }

    /**
     * @desc Encrypt specified password. 
     *
     * @param   string  $password
     * @return  string
     */
    static private function _encrypt($password) 
    {
        return self::_encryption()->encrypt($password);
    }

    /**
     * @desc Return encryption algorithm instantiation specified in the settings. 
     *
     * @return  Security_Encryption_Interface
     */
    static private function _encryption() 
    {
        static $instance;
        return isset($instance)
            ? $instance
            : $instance = Kernel::factory('Security_Encryption', Kernel_Config::get('security', 'encryption'));
    }

    /**
     * @desc Send email. 
     *
     * @param   integer $account_id
     * @param   integer $message
     * @param   array   $vars
     * @return  boolean
     */
    static private function _send($account_id, $action, $role, $vars = array()) 
    {
        $_messages = array(
            Security_Subject::ADMIN => 'backend',
            Security_Subject::USER  => 'user',
        );

        $prefix = $_messages[$role];

        $class = ucfirst($prefix . '_Mail_Message');
        $name = $prefix . '/' . $action;

        $message = new $class(array(
            'name' => $name,
            'profile' => self::getProfileId($account_id, $role)
        ));

        return $message->assign($vars)->send();
    }
    
}
